//
//  TypeEncoding.swift
//  MacDemo
//
//  Created by FH on 2020/11/19.
//  Copyright © 2020 FH. All rights reserved.
//

import Foundation

fileprivate let rawTypeRegex = try! Regex(pattern: "^(const\\s+)?(.+?)(\\s+\\*)?$",
                                          groupNames: "const", "rawType", "pointer")

func typeEncoding(declType: String,
                  typeStr: String,
                  isComplexType: Bool,
                  realType: inout String,
                  _ allNodes: [String: AstNode]) -> String {
    var rawType = typeStr
    if isComplexType {
        rawType = ""
    } else if let findNode = allNodes[declType], findNode.isMainNode {
        if findNode.type == .interface || findNode.type == .protocol {
            rawType = ""
        } else if findNode.type == .enumerate {
            rawType = findNode.declType!
            realType = rawType
        }
    }
    if rawType.count > 0 {
        let match = rawTypeRegex.findFirst(in: rawType)!
        let hasConst = match.group(named: "const") != nil
        let hasPointer = match.group(named: "pointer") != nil
        if (hasConst || hasPointer), let type = match.group(named: "rawType") {
            var encoding = _typeEncoding(rawTypeStr: type)
            if hasPointer && encoding == "@" {
                return "@"
            } else if hasConst && hasPointer && type == "char" {
                return "r*"
            } else {
                if hasPointer {
                    encoding = "^\(encoding)"
                }
                if hasConst {
                    encoding = "r\(encoding)"
                }
                return encoding
            }
        } else {
            return _typeEncoding(rawTypeStr: rawType)
        }
    }
    return "@"
}

fileprivate func _typeEncoding(rawTypeStr: String) -> String {
    switch rawTypeStr {
    case "int":
        return "i"
    case "float", "CGFloat":
        return "f"
    case "double", "NSTimeInterval":
        return "d"
    case "NSInteger":
        return "q"
    case "NSUInteger":
        return "Q"
    case "void":
        return "v"
    case "BOOL", "bool":
        return "B"
    case let str where str.hasSuffix("Ref") && str.hasPrefix("C"):
        if str == "CVPixelBufferRef" {
            return "^{__CVBuffer=}"
        } else if str == "CMSampleBufferRef" {
            return "^{opaqueCMSampleBuffer=}"
        }
        let index = str.index(str.startIndex, offsetBy: (str.count - 3))
        return "^{\(str[..<index])=}"
    default:
        if rawTypeStr.hasPrefix("NS") {
            return "@"
        } else {
            if rawTypeStr == "CGRect" {
                let value = NSValue(rect: CGRect.zero)
                return String(cString: value.objCType)
            } else if rawTypeStr == "CGPoint" {
                let value = NSValue(point: CGPoint.zero)
                return String(cString: value.objCType)
            } else if rawTypeStr == "CGSize" {
                let value = NSValue(size: CGSize.zero)
                return String(cString: value.objCType)
            } else if rawTypeStr == "CMTime" {
                let value = NSValue(time: CMTime.zero)
                return String(cString: value.objCType)
            }
            return "{\(rawTypeStr)}"
        }
    }
}

func formatIntHex(value: String?, rawType: String) -> String? {
    if rawType == "int" || rawType == "unsigned int" || rawType == "NSInteger" || rawType == "NSUInteger" {
        if let value = value, let intValue = Int(value) {
            return String(format: "0x%08X", intValue)
        }
    }
    return nil
}

func getArgValue(invocation: NSInvocationPrivate,
                 method: AstNode,
                 forReturnValue: Bool,
                 forBlockValue: Bool) -> [String : Any] {
    var result = [String : Any]()
    if forReturnValue {
        if method.value!.isReturnVoid {
            result["return"] = ()
        } else {
            let valueType = method.value!.isTypealiasType ? method.value!.rawType! : method.value!.valueType!
            let returnValue = _getArgValue(invocation: invocation,
                                           valueType: valueType,
                                           index: 0,
                                           isObjCRefType: method.value!.isObjCRefType,
                                           forReturnValue: true)
            result["return"] = returnValue
        }
    } else {
        var index = 2
        // get `sender` param value
        if forBlockValue {
            let senderValue = _getArgValue(invocation: invocation,
                                           valueType: "NSString",
                                           index: index,
                                           isObjCRefType: true,
                                           forReturnValue: false)
            result["sender"] = senderValue
            index += 1
        }
        // get original parameters value in block
        for param in method.children {
            let valueType = param.value!.isTypealiasType ? param.value!.rawType! : param.value!.valueType!
            var paramValue = _getArgValue(invocation: invocation,
                                          valueType: valueType,
                                          index: index,
                                          isObjCRefType: forBlockValue || param.value!.isObjCRefType,
                                          forReturnValue: false)
            if forBlockValue, let objcValue = paramValue as? NSValue {
                if valueType == "NSInteger" || valueType == "int" {
                    paramValue = objcValue.value(of: Int.self)
                } else if valueType == "NSUInteger" {
                    paramValue = objcValue.value(of: UInt.self)
                } else if valueType == "BOOL" || valueType == "bool" {
                    paramValue = objcValue.value(of: Bool.self)
                }                
            }
            result[param.name] = paramValue
            index += 1
        }
    }
    return result
}

fileprivate func _getArgValue(invocation: NSInvocationPrivate,
                              valueType: String,
                              index: Int,
                              isObjCRefType: Bool,
                              forReturnValue: Bool) -> Any? {
    let getValueBlock = { (arg: UnsafeMutableRawPointer, index: Int, forReturnValue: Bool) in
        if forReturnValue {
            invocation.getReturnValue(arg)
        } else {
            invocation.getArgument(arg, at: index)
        }
    }

    var result: Any! = ()
    if isObjCRefType {
        result = ObjCInvokeProxy.getObjcRefValue(withInvocation: invocation, index: index, forReturnValue: forReturnValue)
    } else if valueType == "int" {
        var arg: Int32 = 0
        getValueBlock(&arg, index, forReturnValue)
        result = arg
    } else if valueType == "float" || valueType == "CGFloat" {
        var arg: Float = 0
        getValueBlock(&arg, index, forReturnValue)
        result = arg
    } else if valueType == "double" || valueType == "NSTimeInterval" {
           var arg: Double = 0
           getValueBlock(&arg, index, forReturnValue)
           result = arg
    } else if valueType == "NSInteger" {
           var arg: Int = 0
           getValueBlock(&arg, index, forReturnValue)
           result = arg
    } else if valueType == "NSUInteger" {
           var arg: UInt = 0
           getValueBlock(&arg, index, forReturnValue)
           result = arg
    } else if valueType == "BOOL" || valueType == "bool" {
           var arg: Bool = false
           getValueBlock(&arg, index, forReturnValue)
           result = arg
    } else if valueType == "CGRect" {
        var arg: CGRect = CGRect.zero
        getValueBlock(&arg, index, forReturnValue)
        result = arg
    } else if valueType == "CGPoint" {
        var arg: CGPoint = CGPoint.zero
        getValueBlock(&arg, index, forReturnValue)
        result = arg
    } else if valueType == "CGSize" {
        var arg: CGSize = CGSize.zero
        getValueBlock(&arg, index, forReturnValue)
        result = arg
    } else if valueType == "CMTime" {
        var arg: CMTime = CMTime.zero
        getValueBlock(&arg, index, forReturnValue)
        result = arg
    } else {
        print(">>> not support type: \(valueType) in _getArgValue()")
    }
    return result
}

func castEnumToNativeValue(value: Any, rawType: String) -> Any {
    var enumValue = value
    return withUnsafeMutablePointer(to: &enumValue) { pointer -> Any in
        var castValue: Any!
        if rawType == "NSInteger" {
            let size = MemoryLayout<Int>.size
            castValue = pointer.withMemoryRebound(to: Int.self, capacity: size) { castPointer -> Any in
                return castPointer.pointee
            }
        }
        if rawType == "NSUInteger" {
            let size = MemoryLayout<UInt>.size
            castValue = pointer.withMemoryRebound(to: UInt.self, capacity: size) { castPointer -> Any in
                return castPointer.pointee
            }
        }
        return castValue!
    }
}

func castStringToEnumValue(_ enumTypeStr: String, _ valueStr: String, engine: ScriptEngine) -> Any? {
    var enumType = enumTypeStr
    if enumTypeStr.starts(with: "__C.") {
        enumType =  enumTypeStr.replacingOccurrences(of: "__C.", with: "")
    }
    if let enumNode = engine.findAstNode(by: enumType), enumNode.type == .enumerate {
        return castStringToValue(value: valueStr, valueType: enumNode.declType!)
    }
    return nil
}

func castAnyToObjCValue(value: Any, valueType: String, isObjCRefType: Bool) -> AnyObject {
    if valueType == "int" {
        let castValue = value as! Int32
        return NSNumber(value: castValue)
    } else if valueType == "float" || valueType == "CGFloat" {
        let castValue = value as! Float
        return NSNumber(value: castValue)
    } else if valueType == "double" || valueType == "NSTimeInterval" {
        let castValue = value as! Double
        return NSNumber(value: castValue)
    } else if valueType == "NSInteger" {
        let castValue = value as! Int
        return NSNumber(value: castValue)
    } else if valueType == "NSUInteger" {
        let castValue = value as! UInt
        return NSNumber(value: castValue)
    } else if valueType == "BOOL" || valueType == "bool" {
        let castValue = value as! Bool
        return NSNumber(value: castValue)
    } else if valueType == "CGRect" {
        let castValue = value as! CGRect
        return NSValue(rect: castValue)
    } else if valueType == "CGPoint" {
        let castValue = value as! CGPoint
        return NSValue(point: castValue)
    } else if valueType == "CGSize" {
        let castValue = value as! CGSize
        return NSValue(size: castValue)
    } else if valueType == "CMTime" {
        let castValue = value as! CMTime
        return NSValue(time: castValue)
    } else {
        return value as AnyObject
    }
}

func castObjCToAnyValue(value: AnyObject, valueType: String) -> Any {
    if let objcNumber = value as? NSNumber {
        if valueType == "int" {
            return objcNumber.intValue
        } else if valueType == "float" {
            return objcNumber.floatValue
        } else if valueType == "CGFloat" {
            return CGFloat(objcNumber.floatValue)
        } else if valueType == "double" {
            return objcNumber.doubleValue
        } else if valueType == "NSTimeInterval" {
            return TimeInterval(objcNumber.doubleValue)
        } else if valueType == "NSInteger" {
            return objcNumber.intValue
        } else if valueType == "NSUInteger" {
            return objcNumber.uintValue
        } else if valueType == "BOOL" || valueType == "bool" {
            return objcNumber.boolValue
        }
    } else if let objcValue = value as? NSValue {
        if valueType == "CGRect" {
            return objcValue.rectValue
        } else if valueType == "CGPoint" {
            return objcValue.pointValue
        } else if valueType == "CGSize" {
            return objcValue.sizeValue
        } else if valueType == "CMTime" {
            return objcValue.timeValue
        }
    }
    return value
}

func castStringToValue(value: String, valueType: String) -> Any? {
    if valueType == "int" {
        return Int32(value)
    } else if valueType == "NSInteger" {
        return Int(value)
    } else if valueType == "NSUInteger" {
        return UInt(value)
    } else if valueType == "float" {
        return Float(value)
    } else if valueType == "CGFloat" {
        return Float(value).map { CGFloat($0) }
    } else if valueType == "double" {
        return Double(value)
    } else if valueType == "NSTimeInterval" {
        return Double(value).map { TimeInterval($0) }
    } else if valueType == "CGSize" {
    #if HERMES_MAC
        let match = Tuple2Regex.findFirst(in: value)
        if let widthStr = match?.group(named: "item1"), let heightStr = match?.group(named: "item2"),
            let width = Float(widthStr), let height = Float(heightStr) {
            return CGSize(width: width, height: height)
        }
    #endif
    } else if valueType == "CGRect" {
    #if HERMES_MAC
        let match = Tuple4Regex.findFirst(in: value)
        if let xStr = match?.group(named: "item1"), let yStr = match?.group(named: "item2"),
            let widthStr = match?.group(named: "item3"), let heightStr = match?.group(named: "item4"),
            let x = Float(xStr), let y = Float(yStr),
            let width = Float(widthStr), let height = Float(heightStr) {
            return CGRect(x: x, y: y, width: width, height: height)
        }
    #endif
    }
    return value
}

fileprivate let enumValueDescRegex = try! Regex(pattern: #"Agora.+?\((\d+)\)"#, groupNames: "rawValue")
func castJsonStringToModel(_ jsonStr: String, modelType: String, engine: ScriptEngine) -> Any? {
    if let modelNode = engine.findAstNode(by: modelType), modelNode.type == .interface,
        let data = jsonStr.data(using: .utf8), let json = try? JSONDecoder().decode([String : String].self, from: data) {
        let instance = engine.createInstance(modelType, false, nil, nil)
        for node in modelNode.children where node.type == .property {
            if let value = json[node.name] {
                var rawValue: Any? = nil
                if node.isDeclComplexType {
                    if let container = node.declComplexContainerType,
                        container == "NSDictionary" || container == "NSMutableDictionary",
                        let propType = node.declComplexInnerType {
                        if let data = value.data(using: .utf8),
                            let dictJson = try? JSONDecoder().decode([String : String].self, from: data) {
                            let modelDict = NSMutableDictionary()
                            for (key, item) in dictJson {
                                if let model = castJsonStringToModel(item, modelType: propType, engine: engine) {
                                    modelDict[key] = model
                                }
                            }
                            rawValue = modelDict
                        }
                    } else if let container = node.declComplexContainerType,
                        ((container == "NSArray" || container == "NSMutableArray")
                            || (container == "NSSet" || container == "NSMutableSet")),
                        let propType = node.declComplexInnerType {
                        if let data = value.data(using: .utf8),
                            let listJson = try? JSONDecoder().decode([String].self, from: data) {
                            let isContainerSet = container == "NSSet" || container == "NSMutableSet"
                            let modelList = NSMutableArray()
                            let modelSet = NSMutableSet()
                            for item in listJson {
                                if let model = castJsonStringToModel(item, modelType: propType, engine: engine) {
                                    if isContainerSet {
                                        modelSet.add(model)
                                    } else {
                                        modelList.add(model)
                                    }
                                }
                            }
                            rawValue = modelList
                        }
                    }
                } else if node.declType!.hasPrefix("Agora") {
                    if let propNode = engine.findAstNode(by: node.declType!) {
                        if propNode.type == .enumerate {
                            let match = enumValueDescRegex.findFirst(in: value)
                            if let enumRawValue = match?.group(named: "rawValue") {
                                rawValue = castStringToEnumValue(node.declType!, enumRawValue, engine: engine)
                            }
                        } else if propNode.type == .interface {
                            rawValue = castJsonStringToModel(value, modelType: propNode.name, engine: engine)
                        }
                    }
                } else {
                    rawValue = castStringToValue(value: value, valueType: node.declType!)
                }
                engine.setValue(rawValue, node.name, modelType, instance)
            }
        }
        return instance
    }
    return nil
}

//REMARK: dump anyValue to json string

func dumpAnyToJsonString(forSerialization: Bool=false, value: Any, valueType: String?, engine: ScriptEngine) -> String {
    if let valueType = valueType, let node = engine.findAstNode(by: valueType) {
        if node.type == .enumerate {
            return engine.getEnumDescription(value, enumType: node.declType!, isEnumValue: true)
        } else if node.type == .interface {
            return dumpAnyToJsonString(forSerialization: forSerialization, value: value, node: node, engine: engine) ?? ""
        }
    }
    return String(reflecting: value)
}

func dumpAnyToJsonString(forSerialization: Bool=false, value: Any, node: AstNode, engine: ScriptEngine) -> String? {
    var modelNode: AstNode! = nil
    if node.type == .property || node.type == .parameter {
        modelNode = engine.findAstNode(by: node.declType!)
    }
    if let valueObject = value as? NSObject,
        (node.type == .interface || (node.value!.isObjCRefType && modelNode != nil && modelNode!.type == .interface)) {
        if node.type == .interface {
            modelNode = node
        }
        let properties = dumpModelToJsonItems(forSerialization: forSerialization,
                                              valueObject: valueObject,
                                              node: modelNode,
                                              engine: engine)
        if properties.count > 0 {
            let data = try! JSONEncoder().encode(properties)
            return String(data: data, encoding: .utf8)
        } else {
            return "\(modelNode.name){}"
        }
    } else if let enumRawType = node.value!.rawType, enumRawType.count > 0 {
        return engine.getEnumDescription(value, enumType: node.declType!, isEnumValue: true)
    } else {
        return String(reflecting: value)
    }
}

func dumpAnyToJsonString(forSerialization: Bool=false, valueOrNil: Any?, node: AstNode, engine: ScriptEngine) -> String? {
    if let value = valueOrNil {
        return dumpAnyToJsonString(forSerialization: forSerialization, value: value, node: node, engine: engine)
    }
    return nil
}

func dumpAnyToJsonString(forSerialization: Bool=false, valueOrNil: Any?, className: String, engine: ScriptEngine) -> String? {
    if let node = engine.findAstNode(by: className), node.type == .interface {
        return dumpAnyToJsonString(forSerialization: forSerialization, valueOrNil: valueOrNil, node: node, engine: engine)
    }
    return nil
}

func dumpAnyToJsonString(forSerialization: Bool=false, values: [Any?], nodes: [AstNode], engine: ScriptEngine) -> String? {
    if values.count == nodes.count {
        var list = [AnyEncodable]()
        for i in 0..<nodes.count {
            let value = values[i]
            let node = nodes[i]
            let jsonString = dumpAnyToJsonString(forSerialization: forSerialization, valueOrNil: value, node: node, engine: engine)
            list.append(AnyEncodable(jsonString))
        }
        let data = try! JSONEncoder().encode(list)
        return String(data: data, encoding: .utf8)
    }
    return nil
}

func dumpAnyToJsonString(forSerialization: Bool=false, values: [Any?]?, methodName: String, engine: ScriptEngine) -> String? {
    if let values = values, values.count > 0, let methodNode = engine.findAPIMethodNode(by: methodName) {
        var propNodes = [AstNode]()
        for prop in methodNode.children {
            propNodes.append(prop)
        }
        return dumpAnyToJsonString(forSerialization: forSerialization, values: values, nodes: propNodes, engine: engine)
    }
    return nil
}

func dumpAnyToJsonString(forSerialization: Bool=false, values: [String : Any]?, methodName: String, engine: ScriptEngine) -> String? {
    if let values = values, values.count > 0,
        let node = engine.findAPIMethodNode(by: methodName), values.count == node.children.count {
        var argValues = [Any?]()
        for param in node.children {
            argValues.append(values[param.name])
        }
        return dumpAnyToJsonString(forSerialization: forSerialization, values: argValues, nodes: node.children, engine: engine)
    }
    return nil
}

func dumpAnyToJsonString(forSerialization: Bool,
                         values: [String : Any]?,
                         delegateMethodName: String,
                         engine: ScriptEngine,
                         ignoreSender: Bool) -> String? {
    if let values = values, values.count > 0,
        let node = engine.findDelegateMethodNode(by: delegateMethodName), values.count == node.children.count {
        
        var ignoreFirstArg = false
        let params = node.children
        if ignoreSender {
            if values.count > 1 && params.count > 1,
                let node = engine.findAstNode(by: params[0].declType!), node.type == .interface {
                ignoreFirstArg = true
            }
        }
        let argParams = ignoreFirstArg ? Array(params.dropFirst()) : params
        var argValues = [Any?]()
        for param in node.children {
            let value = values[param.name]
            if ignoreFirstArg {
                ignoreFirstArg = false
                if String(describing: type(of: value!)) == param.declType! {
                    continue
                }
            }
            argValues.append(value)
        }

        return dumpAnyToJsonString(forSerialization: forSerialization,
                                   values: argValues,
                                   nodes: argParams,
                                   engine: engine)
    }
    return nil
}

// REMARK: dump model's jsonItems

func dumpModelToJsonItems(forSerialization: Bool,
                          valueObject: AnyObject,
                          node: AstNode,
                          engine: ScriptEngine) -> [String : AnyEncodable] {
    var properties = [String : AnyEncodable]()
    for prop in node.children where prop.type == .property {
        let propValue = engine.getValue(prop.name, valueObject, node)
        if prop.isDeclComplexType {
            if let containerType = prop.declComplexContainerType,
                let innerType = prop.declComplexInnerType,
                let innerNode = engine.findAstNode(by: innerType) {
                if (containerType == "NSDictionary" || containerType == "NSMutableDictionary") {
                    if let propValue = propValue, let dictObject = propValue as? NSDictionary {
                        var items = [String : AnyEncodable]()
                        for (key, value) in dictObject {
                            if let keyStr = key as? String, let valueObject = value as? NSObject {
                                let subProperties = dumpModelToJsonItems(forSerialization: forSerialization,
                                                                         valueObject: valueObject,
                                                                         node: innerNode,
                                                                         engine: engine)
                                let subObject = AnyEncodeObject(items: subProperties)
                                if forSerialization, let data = try? JSONEncoder().encode(subObject),
                                    let objectJson = String(data: data, encoding: .utf8) {
                                    items[keyStr] = AnyEncodable(objectJson)
                                } else {
                                    items[keyStr] = AnyEncodable(subObject)
                                }
                            }
                        }
                        let object = AnyEncodeObject(items: items)
                        if forSerialization, let data = try? JSONEncoder().encode(object),
                            let objectJson = String(data: data, encoding: .utf8) {
                            properties[prop.name] = AnyEncodable(objectJson)
                        } else {
                            properties[prop.name] = AnyEncodable(object)
                        }
                    } else {
                        if forSerialization {
                            properties[prop.name] = AnyEncodable("nil")
                        } else {
                            let emptyValue: [String : String]? = nil
                            properties[prop.name] = AnyEncodable(emptyValue)
                        }
                    }
                } else if (containerType == "NSArray" || containerType == "NSMutableArray") {
                    if let propValue = propValue, let arrObject = propValue as? NSArray {
                        var items = [AnyEncodable]()
                        for value in arrObject {
                            if let valueObject = value as? NSObject {
                                let subProperties = dumpModelToJsonItems(forSerialization: forSerialization,
                                                                         valueObject: valueObject,
                                                                         node: innerNode,
                                                                         engine: engine)
                                let object = AnyEncodeObject(items: subProperties)
                                items.append(AnyEncodable(object))
                            }
                        }
                        let list = AnyEncodeList(items: items)
                        if forSerialization, let data = try? JSONEncoder().encode(list),
                            let listJson = String(data: data, encoding: .utf8) {
                            properties[prop.name] = AnyEncodable(listJson)
                        } else {
                            properties[prop.name] = AnyEncodable(list)
                        }
                    } else {
                        if forSerialization {
                            properties[prop.name] = AnyEncodable("nil")
                        } else {
                            let emptyValue: [String]? = nil
                            properties[prop.name] = AnyEncodable(emptyValue)
                        }
                    }
                } else if (containerType == "NSSet" || containerType == "NSMutableSet") {
                    if let propValue = propValue, let setObject = propValue as? NSSet {
                        var items = [AnyEncodable]()
                        for value in setObject {
                            if let valueObject = value as? NSObject {
                                let subProperties = dumpModelToJsonItems(forSerialization: forSerialization,
                                                                         valueObject: valueObject,
                                                                         node: innerNode,
                                                                         engine: engine)
                                let object = AnyEncodeObject(items: subProperties)
                                items.append(AnyEncodable(object))
                            }
                        }
                        let set = AnyEncodeList(items: items)
                        if forSerialization, let data = try? JSONEncoder().encode(set),
                            let setJson = String(data: data, encoding: .utf8) {
                            properties[prop.name] = AnyEncodable(setJson)
                        } else {
                            properties[prop.name] = AnyEncodable(set)
                        }
                    } else {
                        if forSerialization {
                            properties[prop.name] = AnyEncodable("nil")
                        } else {
                            let emptyValue: [String]? = nil
                            properties[prop.name] = AnyEncodable(emptyValue)
                        }
                    }
                }
            }
        } else if prop.value!.isObjCRefType, let classNode = engine.findAstNode(by: prop.declType!) {
            if let propValue = propValue, let valueObject = propValue as? NSObject {
                let subProperties = dumpModelToJsonItems(forSerialization: forSerialization,
                                                         valueObject: valueObject,
                                                         node: classNode,
                                                         engine: engine)
                let object = AnyEncodeObject(items: subProperties)
                if forSerialization, let data = try? JSONEncoder().encode(object),
                    let objectJson = String(data: data, encoding: .utf8) {
                    properties[prop.name] = AnyEncodable(objectJson)
                } else {
                    properties[prop.name] = AnyEncodable(object)
                }
            } else {
                if forSerialization {
                    properties[prop.name] = AnyEncodable("nil")
                } else {
                    let emptyValue: [String : String]? = nil
                    properties[prop.name] = AnyEncodable(emptyValue)
                }
            }
        } else {
            let valueStr = dumpAnyToJsonString(forSerialization: forSerialization,
                                               valueOrNil: propValue,
                                               node: prop,
                                               engine: engine)
            properties[prop.name] = AnyEncodable(valueStr)
        }
    }
    return properties
}

// TODO: 抽取dump array/dictionary
